/*
 * GameFieldPanel.java
 *
 * Created on January 18, 2007, 6:35 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package net.sf.iSnake.UI;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.JPanel;
import net.sf.iSnake.configuration.iSnakeConfiguration;
import net.sf.iSnake.core.GameAreaCoordinate;
import net.sf.iSnake.core.GameController;
import net.sf.iSnake.core.KeyboardInputHandler;
import net.sf.iSnake.core.PlayerPreferences;

/**
 *
 * @author thelinuxmaniac
 */
public class GameFieldPanel extends JPanel  {
    
    private iSnakeConfiguration conf;
    private GameController gameController;
    
    private RenderingHints hintForSpeedProcessing;
    private RenderingHints hintForQualityProcessing;
    
    private BasicStroke strokeForSnakeHead;
    private BasicStroke strokeForSnakeBody;
    private BasicStroke strokeForSnakeTail;
    private BasicStroke strokeForWall;
    private BasicStroke strokeForCurrentFood;
    private BasicStroke strokeForNextFood;
    private BasicStroke strokeForNULL;
    
    // Use of Float type coordinates speeds up the drawing process :)
    private Rectangle2D.Float snakeHeadShape;
    private Rectangle2D.Float snakeBodyShape;
    private Rectangle2D.Float snakeTailShape;
    private Rectangle2D.Float currentFoodShape;
    private Rectangle2D.Float nextFoodShape;
    private Rectangle2D.Float wallShape;
    private Rectangle2D.Float NULLShape;
    
    // Paint for various game field objects
    private Paint snakeHeadPaint;
    private Paint snakeBodyPaint;
    private Paint snakeTailPaint;
    private Paint currentFoodPaint;
    private Paint nextFoodPaint;
    private Paint wallPaint;
    private Paint NULLPaint;
    
    private float insetForWall;
    private float insetForFood;
    private float insetForSnake;
    
    private float gameFieldObjectWidth;
    private float gameFieldObjectHeight;
    
    public static final Short blocksInWidth = 64;       // No of blocks that is expected along the width: in our case it is 640/10 = 64
    public static final Short blocksInHeight = 48;      // No of blocks that is expected along the height: in our case it is 480/10 = 48
    
    /**
     * This vector is used by the GameFieldPanel to draw respective snakes in their respective color
     * This tables is created so that it becomes easy for the drawGameField() method to draw the snakes
     * without any hassel of getting the colors of each snake every time the field is repainted
     */
    private Vector playersSnakeColor = new Vector(10);
    
    /** Creates a new instance of GameFieldPanel */
    public GameFieldPanel(iSnakeConfiguration conf, GameController gameController,KeyboardInputHandler localPlayerKeyboardInputHandler) {
        this.conf = conf;
        this.setGameController(gameController);
        
        setPreferredSize(conf.GameFieldPanelDimension);
        setBackground(Color.BLACK);
        getPlayersSnakeColor().add(0,Color.BLACK);

        /**
         * @TODO Implement using Keyboard focus manager so that the game field panel focus is not lost
         */
        setFocusable(true);         // To capture the input keystrokes
        this.addKeyListener(localPlayerKeyboardInputHandler);
        // @TODO get the focus using keyoard focus manager
        requestFocus();
        
        // addKeyListener(gameController.getLocalSnake().getSnakeInputHandler());
        
        // @TODO change to the Rectangle2D object so that exact boundary of game field can be shown and it can be gradient painted
        // setBorder(BorderFactory.createLineBorder(Color.GREEN,1));

        // Calculates the size of each block in the game field
        gameFieldObjectWidth = (float) (conf.GameFieldPanelDimension.getWidth()/blocksInWidth);
        gameFieldObjectHeight = (float) (conf.GameFieldPanelDimension.getHeight()/blocksInHeight);
        
//        System.out.println("gameFieldObjectWidth "+(int)gameFieldObjectWidth);
//        System.out.println("gameFieldObjectHeight "+(int)gameFieldObjectHeight);
        
        /* All the objects must be withing 10x10 pixels
         * This inset value determines the spacing between the 10x10 boundary of each object and the actual object
         * eg: if insetForFood = 2.0F, the Food will have the following dimension (10-2)x(10-2)= 8x8 pixels
         */
        insetForFood = 0.0F;
        insetForSnake = 0.0F;
        insetForWall = 0.0F;
        
        hintForSpeedProcessing = new RenderingHints(null);
        hintForSpeedProcessing.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_SPEED);
        hintForSpeedProcessing.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
        
        hintForQualityProcessing = new RenderingHints(null);
        hintForQualityProcessing.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
        hintForQualityProcessing.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        strokeForWall = new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND);
        strokeForCurrentFood = new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND);
        strokeForNextFood = new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND);
        strokeForNULL = new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND);
        
        snakeHeadShape = new Rectangle2D.Float();
        snakeBodyShape = new Rectangle2D.Float();
        snakeTailShape = new Rectangle2D.Float();
        currentFoodShape = new Rectangle2D.Float();
        nextFoodShape = new Rectangle2D.Float();
        wallShape = new Rectangle2D.Float();
        NULLShape = new Rectangle2D.Float();
        
        /*
         * @TODO Modify the painting method to GradientPaint which looks better
         */
        snakeHeadPaint = Color.WHITE;
        snakeBodyPaint = Color.WHITE;
        snakeTailPaint = Color.WHITE;
        currentFoodPaint = conf.GameObjectColorCFood;
        nextFoodPaint = conf.GameObjectColorNFood;
        wallPaint = conf.GameObjectColorWall;
        NULLPaint = conf.GameObjectColorNULL;
    }

    
    public void drawSnakeHead(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        snakeHeadPaint = gameObjectColor;
        snakeHeadShape.setRect(coordinate.getXCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForSnake,gameFieldObjectHeight-2*insetForSnake);
//        g2.draw(snakeHeadShape);
        g2.setPaint(snakeHeadPaint);
        g2.fill(snakeHeadShape);
    }

    public void drawSnakeBody(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        snakeBodyPaint = gameObjectColor;
        snakeBodyShape.setRect(coordinate.getXCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForSnake,gameFieldObjectHeight-2*insetForSnake);
//        g2.draw(snakeBodyShape);
        g2.setPaint(snakeBodyPaint);
        g2.fill(snakeBodyShape);        
    }
    
    public void drawSnakeTail(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        snakeTailPaint = gameObjectColor;
        snakeTailShape.setRect(coordinate.getXCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForSnake+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForSnake,gameFieldObjectHeight-2*insetForSnake);
//        g2.draw(snakeTailShape);
        g2.setPaint(snakeTailPaint);
        g2.fill(snakeTailShape);        
    }
    
    public void drawWall(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        wallPaint = gameObjectColor;
        wallShape.setRect(coordinate.getXCoordinateInPixel()+insetForWall+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForWall+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForWall,gameFieldObjectHeight-2*insetForWall);
        //g2.setStroke(strokeForWall);
        //g2.draw(wallShape);
        g2.setPaint(wallPaint);
        g2.fill(wallShape);
        // For debugging purpose
        //System.out.println("x= "+(int) coordinate.getXCoordinateInPixel()+" y= "+(int) coordinate.getYCoordinateInPixel());
        //System.out.println("w= "+(int) gameFieldObjectWidth+ "h= "+(int) gameFieldObjectHeight);
    }
    
    public void drawCurrentFood(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        currentFoodPaint = gameObjectColor;
        currentFoodShape.setRect(coordinate.getXCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForFood,gameFieldObjectHeight-2*insetForFood);
//        g2.draw(currentFoodShape);
        g2.setPaint(currentFoodPaint);
        g2.fill(currentFoodShape);
        
        // For debugging purpose
        //System.out.println("x= "+(int) coordinate.getXCoordinateInPixel()+" y= "+(int) coordinate.getYCoordinateInPixel());
        //System.out.println("w= "+(int) gameFieldObjectWidth+ "h= "+(int) gameFieldObjectHeight);
    }
    
    public void drawNextFood(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        nextFoodPaint = gameObjectColor;
        nextFoodShape.setRect(coordinate.getXCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForFood,gameFieldObjectHeight-2*insetForFood);
//        g2.draw(nextFoodShape);
        g2.setPaint(nextFoodPaint);
        g2.fill(nextFoodShape);
        
        // For debugging purpose
//        System.out.println("x= "+(int) (coordinate.getXCoordinateInPixel()-insetForFood+conf.GameFieldPanelPadX)+" y= "+(int) (coordinate.getYCoordinateInPixel()-insetForFood+conf.GameFieldPanelPadY));
//        System.out.println("w= "+(int) gameFieldObjectWidth+ " h= "+(int) gameFieldObjectHeight);
    }
    
    public void drawNULL(Graphics2D g2,GameAreaCoordinate coordinate,Color gameObjectColor)
    {
        NULLPaint = gameObjectColor;
        NULLShape.setRect(coordinate.getXCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadX,coordinate.getYCoordinateInPixel()+insetForFood+conf.GameFieldPanelPadY,gameFieldObjectWidth-2*insetForFood,gameFieldObjectHeight-2*insetForFood);
//        g2.draw(nextFoodShape);
        g2.setPaint(NULLPaint);
        g2.fill(NULLShape);
        
        // For debugging purpose
//        System.out.println("x= "+(int) (coordinate.getXCoordinateInPixel()-insetForFood+conf.GameFieldPanelPadX)+" y= "+(int) (coordinate.getYCoordinateInPixel()-insetForFood+conf.GameFieldPanelPadY));
//        System.out.println("w= "+(int) gameFieldObjectWidth+ " h= "+(int) gameFieldObjectHeight);
    }
    
    
    /*
     * To update the contents of GameField
     */
    public void updateGameField()
    {
        repaint();
    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        g2.setColor(Color.GREEN);
        
        Point2D p1 = new Point2D.Float(8,8);
        //Point2D p2 = new Point2D.Float(642,484);
        Point2D p2 = new Point2D.Float(652,494);
        
        Rectangle2D boundary = new Rectangle2D.Float();
        boundary.setFrameFromDiagonal(p1,p2);
        g2.draw(boundary);

        // To test the boundary position
//        testBoundary(g2);
        
        // Change this as required in future
        // @TODO Allow the player to choose rendering hints mode in future

        // No Shadow or anti-aliasing is done for the graphic objects in "hintForSpeedProcessing" mode
        g2.setRenderingHints(hintForSpeedProcessing);
        
        // Shadow drop and anti-aliasing is done for the graphic objects in "hintForQualityProcessing" mode
//        g2.setRenderingHints(hintForQualityProcessing);
        drawGameFieldMatrix(g2);
    }
    
    /**
     * Analyses the gameFieldMatrix and draws the gameField panel according to the matrix contents
     * @param g2 The reference of graphics 2D objject
     */
    private void drawGameFieldMatrix(Graphics2D g2)
    {
//        System.out.println("drawGameFieldMatrix() invoked");
        GameAreaCoordinate coordinate = new GameAreaCoordinate();
        Short gameFieldObject = new Short("0");
        short playerId;
        short indexSnakeId;
       
        for(short i=0;i<gameController.GAME_AREA_MATRIX_WIDTH;i++) {
            for(short j=0;j<gameController.GAME_AREA_MATRIX_HEIGHT;j++)  {
                coordinate.set(i,j);
//                System.out.println("coordinate= "+coordinate.toString());
                gameFieldObject = gameController.getObjectFromGameArea(coordinate);
                if(gameFieldObject >= 10 && gameFieldObject<=999)   {
                    // the gameFieldObject belongs to players snake
                    playerId = new Integer((gameFieldObject - (short) (gameFieldObject % 10)) / 10).shortValue();
                    indexSnakeId = (short) (gameFieldObject % 10);
                    
//                    System.out.println("indexSnakeId= "+indexSnakeId+" player_id= "+playerId+" coordinate= "+coordinate.toString());
                    if(indexSnakeId == gameController.INDEX_SNAKE_HEAD)   {
//                        System.out.println("snake head drawn");
                        drawSnakeHead(g2,coordinate,(Color) this.getPlayersSnakeColor(playerId));   // -1 becoz, index in Vector starts from 0
                    }
                    else if(indexSnakeId == gameController.INDEX_SNAKE_TAIL)   {
//                        System.out.println("snake tail drawn");
                        drawSnakeTail(g2,coordinate,(Color) this.getPlayersSnakeColor(playerId));   // -1 becoz, index in Vector starts from 0
                    }
                    // @TODO Implement the functionality of drawing snake body
                }
                
                // if the contents of gameField matrix is zero, it will be draw as blank (ie black rectangle)
                else if(gameFieldObject == gameController.GAME_OBJECT_NULL)  {
                    // implement drawNull() method instead of using drawNextFood
                    drawNULL(g2,coordinate,conf.GameObjectColorNULL);
//                    System.out.println("next food drawn at "+coordinate.toString());
                }
                
                else if(gameFieldObject == gameController.GAME_OBJECT_WALL)  {
                    drawWall(g2,coordinate,conf.GameObjectColorWall);
//                    System.out.println("wall drawn");
                }
                else if(gameFieldObject == gameController.GAME_OBJECT_CURRENT_FOOD)  {
                    drawCurrentFood(g2,coordinate,conf.GameObjectColorCFood);
//                    System.out.println("current food drawn");
                }
                else if(gameFieldObject == gameController.GAME_OBJECT_NEXT_FOOD)  {
                    drawNextFood(g2,coordinate,conf.GameObjectColorNFood);
//                    System.out.println("next food drawn at "+coordinate.toString());
                }
            }
        }
    }

    public GameController getGameController() {
        return gameController;
    }

    public void setGameController(GameController gameController) {
        this.gameController = gameController;
    }
    
    /**
     * The game controller populates the list of colors of all the player's snakes which will
     * be used by the drawGamneField() method to know the color of each snake while drawing
     * This will save from the hassel of recoving the color everytime the gameField is repainted
     * @param ps The collection of players preferences
     */
    public void createPlayersSnakeColorList(Vector ps)
    {
        playersSnakeColor.removeAllElements();  // clear the old color list
        for(Iterator itr = ps.iterator();itr.hasNext();)  {
            PlayerPreferences pref = (PlayerPreferences) itr.next();
            playersSnakeColor.add(pref.getSnakeColor());
        }
    }
    
    public void testBoundary(Graphics2D g2)
    {
/*
        g2.drawRect(conf.GameFieldPanelPadX+0,conf.GameFieldPanelPadY+0,10,10);
        g2.drawRect(conf.GameFieldPanelPadX+10,conf.GameFieldPanelPadY+0,10,10);
        g2.drawRect(conf.GameFieldPanelPadX+0,conf.GameFieldPanelPadY+10,10,10);
        
        
        g2.drawRect(640-conf.GameFieldPanelPadX,10,10,10);
        g2.drawRect(630-conf.GameFieldPanelPadX,10,10,10);
        g2.drawRect(640-conf.GameFieldPanelPadX,10+10,10,10);

        
        g2.drawRect(640-conf.GameFieldPanelPadX,480-10,10,10);
        g2.drawRect(630-conf.GameFieldPanelPadX,480-10,10,10);
        g2.drawRect(640-conf.GameFieldPanelPadX,480-20,10,10);
        
        g2.drawRect(conf.GameFieldPanelPadX,480-10,10,10);
        g2.drawRect(conf.GameFieldPanelPadX,480-20,10,10);
        g2.drawRect(conf.GameFieldPanelPadX+10,480-10,10,10);
 */
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(61,45));
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(62,46));
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(63,47));
        
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(62,0));
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(0,46));
        gameController.setObjectToGameArea(gameController.GAME_OBJECT_NEXT_FOOD,new GameAreaCoordinate(0,0));
    }

    public Vector getPlayersSnakeColor() {
        return playersSnakeColor;
    }

    /*
     * used by drawXXX() method to know the color of each player snake
     * @param player_id the player_id for which color is being requested
     */
    public Color getPlayersSnakeColor(Short player_id) {
        int index = playersSnakeColor.indexOf(gameController.getPlayerPreference(player_id).getSnakeColor());
        if(index != -1)
            return ((Color) playersSnakeColor.get(index));
        else
            return Color.BLACK;
    }

    public void setPlayersSnakeColor(Vector playersSnakeColor) {
        this.playersSnakeColor = playersSnakeColor;
    }
}